using Antlr4.Runtime;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Tree;
using System;
using System.Collections.Generic;
using static AnnotationLanguageParser;

namespace annotation_language
{
    public class AnnotationLanguage
    {
        public Data parser(String annotationed_text)
        {
            Data data = new();
            AntlrInputStream inputStream = new(annotationed_text);
            AnnotationLanguageLexer lexer = new(inputStream);
            CommonTokenStream tokens = new(lexer);
            AnnotationLanguageParser parser = new(tokens);
            ParseTreeWalker walker = new();
            AnnotationLanguageListener listener = new();
            walker.Walk(listener, parser.prog());
            data.regist_framework_patterns = listener.regist_framework_patterns;
            data.target_frame = listener.target_frame;
            data.annotation_patterns = listener.annotation_patterns;
            return data;
        }

    }

    public struct Data
    {
        public Dictionary<Int16, Framework_pattern> regist_framework_patterns;
        public Dictionary<Int16, Annotation_pattern> annotation_patterns;
        public List<(Int16 source_internal_id, Framework_pattern target_framework_pattern, String target_frame_name)> target_frame;
    }

    public class AnnotationLanguageListener : AnnotationLanguageParserBaseListener
    {
        private Int16 counter = 0;
        private Queue<(Framework_pattern target_framework_pattern, String target_frame_name)> _targets = new();
        public Dictionary<Int16, Framework_pattern> regist_framework_patterns = new();
        public List<(Int16 source_internal_id, Framework_pattern target_framework_pattern, String target_frame_name)> target_frame = new();
        public Dictionary<Int16, Annotation_pattern> annotation_patterns = new();
        private Dictionary<String, Dictionary<String, Int16>> framework_name_to_internal_id = new();

        public override void ExitRegister([NotNull] RegisterContext context)
        {
            Framework_pattern framework_pattern = new();
            framework_pattern.framework_name = context.framework_pattern().framework_name().GetText();
            framework_pattern.ontology_name = context.framework_pattern().ontology_name() is null ?
                String.Empty : context.framework_pattern().ontology_name().GetText();
            framework_pattern.temp_id = context.framework_pattern().temp_id() is null ?
                String.Empty : context.framework_pattern().temp_id().GetText();
            framework_pattern.internal_id = this.counter;
            this.regist_framework_patterns.Add(this.counter, framework_pattern);
            this.consume_target();
            this.counter += 1;
            base.ExitRegister(context);
        }

        public override void ExitTarget([NotNull] TargetContext context)
        {
            Framework_pattern target_framework_pattern = new();
            target_framework_pattern.framework_name = context.framework_pattern().framework_name().GetText();
            target_framework_pattern.internal_id = null;
            target_framework_pattern.ontology_name = context.framework_pattern().ontology_name() is null ?
                String.Empty : context.framework_pattern().ontology_name().GetText();
            target_framework_pattern.temp_id = context.framework_pattern().temp_id() is null ?
                String.Empty : context.framework_pattern().temp_id().GetText();
            String frame_name = context.frame_name().GetText();
            this._targets.Enqueue((target_framework_pattern, frame_name));
            base.ExitTarget(context);
        }

        public override void ExitOntology_annotation([NotNull] Ontology_annotationContext context)
        {
            Annotation_pattern annotation_pattern = new();
            annotation_pattern.lexis = context.lexis() is null ?
                String.Empty : context.lexis().GetText();
            annotation_pattern.linkto = context.ontology_name().GetText();
            annotation_pattern.internal_id = this.counter;
            annotation_pattern.annotation_Type = Annotation_type.Ontology;
            this.annotation_patterns.Add(this.counter, annotation_pattern);
            this.consume_target();
            this.counter += 1;
            base.ExitOntology_annotation(context);
        }

        public override void ExitProperty_annotation([NotNull] Property_annotationContext context)
        {
            Annotation_pattern annotation_pattern = new();
            annotation_pattern.lexis = context.lexis() is null ?
                String.Empty : context.lexis().GetText();
            annotation_pattern.linkto = context.property().GetText();
            annotation_pattern.internal_id = this.counter;
            annotation_pattern.annotation_Type = Annotation_type.Property;
            this.annotation_patterns.Add(this.counter, annotation_pattern);
            this.consume_target();
            this.counter += 1;
            base.ExitProperty_annotation(context);
        }

        public override void ExitLexis_annotation([NotNull] Lexis_annotationContext context)
        {
            Annotation_pattern annotation_pattern = new();
            annotation_pattern.lexis = context.lexis().GetText();
            annotation_pattern.linkto = String.Empty;
            annotation_pattern.internal_id = this.counter;
            annotation_pattern.annotation_Type = Annotation_type.Lexis;
            this.annotation_patterns.Add(this.counter, annotation_pattern);
            this.consume_target();
            this.counter += 1;
            base.ExitLexis_annotation(context);
        }
        public override void ExitProg([NotNull] ProgContext context)
        {
            this.complement();
            base.ExitProg(context);
        }

        private void consume_target()
        {
            while (this._targets.Count != 0)
            {
                (Framework_pattern target_framework_pattern, String target_frame_name) target = this._targets.Dequeue();
                this.target_frame.Add((this.counter, target.target_framework_pattern, target.target_frame_name));
            }
        }

        private void complement()
        {
            foreach (KeyValuePair<Int16, Framework_pattern> kv in this.regist_framework_patterns)
            {
                if (this.framework_name_to_internal_id.ContainsKey(kv.Value.framework_name))
                {
                    if (kv.Value.temp_id != String.Empty)
                    {
                        this.framework_name_to_internal_id[kv.Value.framework_name].Add(kv.Value.temp_id, kv.Key);
                    }
                    else
                    {
                        this.framework_name_to_internal_id[kv.Value.framework_name].Add("default", kv.Key);
                    }
                }
                else
                {
                    Dictionary<String, Int16> _d = new();
                    if (kv.Value.temp_id != String.Empty)
                    {
                        _d.Add(kv.Value.temp_id, kv.Key);
                    }
                    else
                    {
                        _d.Add("default", kv.Key);
                    }
                    this.framework_name_to_internal_id.Add(kv.Value.framework_name, _d);
                }
            }
            foreach (var (source_internal_id, target_framework_pattern, target_frame_name) in this.target_frame)
            {
                if (target_framework_pattern.internal_id is null)
                {
                    Int16 internal_id = new();
                    if (target_framework_pattern.temp_id == String.Empty)
                    {
                        internal_id = this.framework_name_to_internal_id[target_framework_pattern.framework_name]["default"];
                    }
                    else
                    {
                        internal_id = this.framework_name_to_internal_id[target_framework_pattern.framework_name][target_framework_pattern.temp_id];
                    }
                    target_framework_pattern.internal_id = internal_id;
                }
            }
        }
    }

    public class Framework_pattern
    {
        public String framework_name { set; get; }
        public String temp_id { set; get; }
        public Int16? internal_id { set; get; }
        public String ontology_name { set; get; }
    }

    public class Annotation_pattern
    {
        public String lexis { set; get; }
        public Int16 internal_id { set; get; }
        public String linkto { set; get; }
        public Annotation_type annotation_Type { set; get; }
    }

    public enum Annotation_type { Lexis, Ontology, Property };

}
